package top.eggcode.plugins.office;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import top.eggcode.common.lang.GraphicCodeUtil;
import top.eggcode.plugins.office.common.DocumentCategory;
import top.eggcode.plugins.office.common.PathGenerator;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * word模板渲染工具
 * 格式要求：{
 * 仅支持docx格式， >=word2007
 * }
 * 插值表达式：{
 * 模板文件中的插值表达式必须是一个整体，否则会被POI分割
 * 如果文档中的插值表达式无法正确被读取，重新设置插值表达式的格式或样式。这是word文档的内部机制导致。
 * }
 * 名词解释：{
 * XWPFParagraph：word中的段落，
 * XWPFRun：最小文本块
 * }
 */
public class WordRender {

    private final PathGenerator pathGenerator;

    public WordRender(PathGenerator pathGenerator) {
        this.pathGenerator = pathGenerator;
    }

    /**
     * 填充条形码
     * 默认边长：长 200 宽 20
     *
     * @param document 可操作文档对象
     * @param props    数据集合
     */
    public void toBarcodeInTable(XWPFDocument document, Map<String, String> props) {
        List<XWPFTable> tableList = document.getTables();
        tableList.forEach(wordTable -> {
            // 表格长度是否大于1
            if (wordTable.getRows().size() > 0) {
                if (checkVariableReference(wordTable.getText())) {
                    List<XWPFTableRow> rowList = wordTable.getRows();
                    rowList.forEach(tableRow -> {
                        List<XWPFTableCell> cellList = tableRow.getTableCells();
                        cellList.forEach(cell -> {
                            if (checkVariableReference(cell.getText())) {
                                // 段落
                                List<XWPFParagraph> paragraphList = cell.getParagraphs();
                                paragraphList.forEach(paragraph -> {
                                    List<XWPFRun> runList = paragraph.getRuns();
                                    runList.forEach(run -> {
                                        String textBlock = run.toString();
                                        // 是否有 $ 字符
                                        if (textBlock.contains("$")) {
                                            String value = getAttributeValue(textBlock, props);
                                            // 值不为空
                                            if (value != null) {
                                                // 替换插值表达式为实际的值
                                                String imageOutPath = this.pathGenerator.getDocument(DocumentCategory.PNG);

                                                GraphicCodeUtil.generateBarCodeByMagnification(imageOutPath, value, 2, 25);
                                                try {
                                                    // 把表达式置空
                                                    run.setText("", 0);

                                                    run.addPicture(new FileInputStream(imageOutPath),
                                                            6,
                                                            "barcode",
                                                            Units.toEMU(200),
                                                            Units.toEMU(20));
                                                } catch (InvalidFormatException | IOException e) {
                                                    e.printStackTrace();
                                                }
                                            }

                                        }
                                    });
                                });
                            }
                        });
                    });
                }
            }
        });

    }

    /**
     * 将条形码渲染到表格
     *
     * @param document 可操作文档
     * @param props    数据结合
     * @param weight   条形码宽度
     * @param height   条形码高度
     */
    public void toBarcodeInTable(XWPFDocument document, Map<String, String> props, int weight, int height) {
        List<XWPFTable> tableList = document.getTables();
        tableList.forEach(wordTable -> {
            // 表格长度是否大于1
            if (wordTable.getRows().size() > 0) {
                if (checkVariableReference(wordTable.getText())) {
                    List<XWPFTableRow> rowList = wordTable.getRows();
                    rowList.forEach(tableRow -> {
                        List<XWPFTableCell> cellList = tableRow.getTableCells();
                        cellList.forEach(cell -> {
                            if (checkVariableReference(cell.getText())) {
                                // 段落
                                List<XWPFParagraph> paragraphList = cell.getParagraphs();
                                paragraphList.forEach(paragraph -> {
                                    List<XWPFRun> runList = paragraph.getRuns();
                                    runList.forEach(run -> {
                                        String textBlock = run.toString();
                                        // 是否有 $ 字符
                                        if (textBlock.contains("$")) {
                                            String value = getAttributeValue(textBlock, props);
                                            // 值不为空
                                            if (value != null) {
                                                // 替换插值表达式为实际的值
                                                String imageOutPath = pathGenerator.getDocument(DocumentCategory.PNG);

                                                GraphicCodeUtil.generateBarCodeByMagnification(imageOutPath, value, 2, 25);
                                                try {
                                                    // 把表达式置空
                                                    run.setText("", 0);

                                                    run.addPicture(new FileInputStream(imageOutPath),
                                                            6,
                                                            "barcode",
                                                            Units.toEMU(weight),
                                                            Units.toEMU(height));
                                                } catch (InvalidFormatException | IOException e) {
                                                    e.printStackTrace();
                                                }
                                            }

                                        }
                                    });
                                });
                            }
                        });
                    });
                }
            }
        });

    }

    /**
     * 填充图片
     *
     * @param document 可操作文档对象
     * @param props    图片路径集合
     */
    public void toImageInTable(XWPFDocument document, Map<String, String> props) {
        List<XWPFTable> tableList = document.getTables();
        tableList.forEach(wordTable -> {
            // 表格长度是否大于1
            if (wordTable.getRows().size() > 0) {
                if (checkVariableReference(wordTable.getText())) {
                    List<XWPFTableRow> rowList = wordTable.getRows();
                    rowList.forEach(tableRow -> {
                        List<XWPFTableCell> cellList = tableRow.getTableCells();
                        cellList.forEach(cell -> {
                            if (checkVariableReference(cell.getText())) {
                                // 段落
                                List<XWPFParagraph> paragraphList = cell.getParagraphs();
                                paragraphList.forEach(paragraph -> {
                                    List<XWPFRun> runList = paragraph.getRuns();
                                    runList.forEach(run -> {
                                        //String textBlock = run.toString().trim();
                                        String textBlock = run.toString();
                                        // 是否有 $ 字符
                                        if (textBlock.contains("$")) {
                                            String value = getAttributeValue(textBlock, props);
                                            // 值不为空
                                            if (value != null) {
                                                // value为图片路径
                                                // 替换插值表达式为实际的值
                                                try {
                                                    // 把表达式置空
                                                    run.setText("", 0);
                                                    // 替换图片
                                                    run.addPicture(new FileInputStream(value),
                                                            6,
                                                            "",
                                                            Units.toEMU(75),
                                                            Units.toEMU(100));
                                                } catch (InvalidFormatException | IOException e) {
                                                    e.printStackTrace();
                                                }
                                            }
                                        }
                                    });
                                });
                            }
                        });
                    });
                }
            }
        });

    }

    /**
     * 渲染填写式表格
     *
     * @param document 文档
     * @param props    数据集合
     */
    public void toTextInTable(XWPFDocument document, Map<String, String> props) {
        List<XWPFTable> tableList = document.getTables();
        tableList.forEach(wordTable -> {
            // 表格长度是否大于1
            if (wordTable.getRows().size() > 0) {
                if (checkVariableReference(wordTable.getText())) {
                    List<XWPFTableRow> rowList = wordTable.getRows();
                    rowList.forEach(tableRow -> {
                        List<XWPFTableCell> cellList = tableRow.getTableCells();
                        cellList.forEach(cell -> {
                            if (checkVariableReference(cell.getText())) {
                                // 段落
                                List<XWPFParagraph> paragraphList = cell.getParagraphs();
                                paragraphList.forEach(paragraph -> {
                                    List<XWPFRun> runList = paragraph.getRuns();
                                    runList.forEach(run -> {
                                        //String textBlock = run.toString().trim();
                                        String textBlock = run.toString();
                                        // 是否有 $ 字符
                                        if (textBlock.contains("$")) {
                                            // 获取属性值
                                            String value = getAttributeValue(textBlock, props);
                                            // 表达式渲染
                                            run.setText(value != null ? value : "", 0);

                                        }
                                    });
                                });
                            }
                        });
                    });
                }
            }
        });
    }

    /**
     * 渲染段落（插值表达式）
     * 使用示例：${变量名}
     * 说明：{
     * 目前是采用 run(最小文本块) 的方式去匹配插值表达式
     * 如果后期因文档内容复杂或不可抗拒的条件导致无法渲染，可以使用正则匹配插值表达式
     * }
     *
     * @param doc   word对象
     * @param props 数据集合
     */
    public void toText(XWPFDocument doc, Map<String, String> props) {
        // 获取段落
        List<XWPFParagraph> paragraphList = doc.getParagraphs();
        paragraphList.forEach(paragraph -> {
            // 获取段落的纯文本形式
            String text = paragraph.getText();
            if (checkVariableReference(text)) {
                // 提取文本框列表
                List<XWPFRun> runList = paragraph.getRuns();
                runList.forEach(run -> {
                    //String textBlock = run.toString().trim();
                    String textBlock = run.toString();
                    // 是否有 $ 字符
                    if (textBlock.contains("$")) {
                        String value = getAttributeValue(textBlock, props);
                        // 表达式渲染
                        run.setText(value != null ? value : "", 0);

                    }
                });
            }
        });
    }

    /**
     * 渲染符号（特殊字符）
     *
     * @param doc   文档
     * @param props 数据
     */
    public void toSymbol(XWPFDocument doc, Map<String, String> props, String font) {
        // 获取段落
        List<XWPFParagraph> paragraphList = doc.getParagraphs();
        paragraphList.forEach(paragraph -> {
            // 获取段落的纯文本形式
            String text = paragraph.getText();
            if (checkVariableReference(text)) {
                // 提取文本框列表
                List<XWPFRun> runList = paragraph.getRuns();
                runList.forEach(run -> {
                    //String textBlock = run.toString().trim();
                    String textBlock = run.toString();
                    // 是否有 $ 字符
                    if (textBlock.contains("$")) {
                        String value = getAttributeValue(textBlock, props);
                        // 值不为空
                        if (value != null) {
                            run.setFontFamily(font);
                            // 替换插值表达式为实际的值
                            run.setText(value, 0);
                        }

                    }
                });
            }
        });
    }

    /**
     * 渲染word表格
     *
     * @param doc   文档
     * @param props 数据集合
     */
    public void toTable(XWPFDocument doc, Map<String, String> props) {

    }

    /**
     * 渲染图片
     *
     * @param doc   文档
     * @param props 数据集合
     */
    public void toImage(XWPFDocument doc, Map<String, String> props) {

    }

    /**
     * 检查插值表达式是否存在
     * 如果内容比较复杂，可以更改匹配方式为正则匹配， 正则表达式：'\$\{.*\}'
     *
     * @param text 文本
     * @return 匹配结果
     */
    public boolean checkVariableReference(String text) {
        return text.contains("$");
    }


    /**
     * 根据变量名（插值表达式中的关键字）查找待填充值
     * 说明：{
     * 目前插值表达式中不支持空格. 正确的例子：${abc}; 错误的例子：${ abc }
     * }
     *
     * @param text  文本块内容
     * @param props 待填充的数据
     * @return 变量值
     */
    public String getVariableValue(String text, Map<String, String> props) {
        // 如果匹配失败，返回空字符串
        String result = "";
        for (Map.Entry<String, String> prop : props.entrySet()) {
            String variableName = prop.getKey();
            String variableValue = prop.getValue();
            StringBuilder exps = new StringBuilder();
            exps.append("${");
            exps.append(
                    // 去掉开头和末尾的空白字符
                    variableName.trim()
            );
            exps.append("}");
            // 表达式存在且待填充内容不为空
            if (text.contains(exps.toString()) && variableValue != null) {
                result = variableValue;
            }
        }
        return result;
    }

    /**
     * 获取
     * 说明：{
     * 目前插值表达式中不支持空格. 正确的例子：${abc}; 错误的例子：${ abc }
     * }
     *
     * @param text  文本块内容
     * @param props 模板数据
     * @return 变量名称
     */
    public String getAttributeValue(String text, Map<String, String> props) {
        for (Map.Entry<String, String> prop : props.entrySet()) {
            String attributeName = prop.getKey();
            if (text.contains(attributeName)) {
                return prop.getValue();
            }
        }
        return null;
    }
}
